Skip to content

feat(android): Add standalone app start tracing#5342

Draft
buenaflor wants to merge 12 commits intomainfrom
feat/standalone-app-start-tracing
Draft

feat(android): Add standalone app start tracing#5342
buenaflor wants to merge 12 commits intomainfrom
feat/standalone-app-start-tracing

Conversation

@buenaflor
Copy link
Copy Markdown
Contributor

@buenaflor buenaflor commented Apr 28, 2026

📜 Description

Adds standalone Android app-start transaction support behind the opt-in enableStandaloneAppStartTracing option and the io.sentry.standalone-app-start-tracing.enable manifest key.

This PR also covers the non-activity startup path so cold starts triggered by broadcasts or foreground services can emit an App Start Cold/Warm transaction without waiting for an activity. The legacy flag-off behavior is preserved: activity app-start data remains nested under the ui.load transaction.

💡 Motivation and Context

Resolves: #5046

Standalone app-start traces make app-start performance visible independently from activity load transactions, and they let Android report app starts where no activity is launched. The implementation keeps the default behavior unchanged and adds tests for the new option, manifest metadata, transaction context trace reuse, app-start metrics, event processing, and activity lifecycle behavior.

💚 How did you test it?

  • ./gradlew spotlessApply apiDump
  • Targeted unit coverage was added for the new option, manifest metadata, app-start metric resolution, event processing, lifecycle integration, and transaction context constructor behavior.
  • Manual end-to-end harness report from standalone_app_start_report.md:

End-to-end Sentry verification

# What it tests Locally emitted shape Trace ID link
1a Flag ON launcher cold start: standalone app-start plus sibling ui.load. app.start + activity.load x2 + process.load + application.load; sibling ui.load d8e97ed6...
1c Flag OFF launcher cold start: legacy nested app-start span. ui.load -> app.start.cold -> process.load + activity.load x2 970768dc...
2a Broadcast cold start with Gradle-plugin timing simulation. app.start + process.load + application.load; no ui.load a39fb47f...
2b Broadcast cold start using Android 15 ApplicationStartInfo. app.start + process.load; no application.load or ui.load e100abb8...
2c Broadcast cold start using API 33 class-load fallback. app.start + process.load; classified Cold 5fb8e75d...
2d Foreground-service cold start through the non-activity path. app.start + process.load + application.load; no ui.load f8ad7a62...
2e Broadcast then launcher: trace reuse without duplicate standalone. Same trace: app.start + sibling ui.load; one standalone only 04ffca08...
2f Flag OFF broadcast regression case. No transaction emitted --

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

buenaflor and others added 10 commits April 2, 2026 14:43
Introduce experimental `enableStandaloneAppStartTracing` option that creates
a separate app start transaction instead of attaching app start as a child
span of the first activity transaction. This is the happy path only (foreground
importance, activity launch, first frame drawn as end time).

The standalone transaction shares the same trace ID as the activity transaction
but is not bound to the scope. App start measurements and child spans (process
init, content providers, application.onCreate) are attached to the standalone
transaction instead of the activity transaction.

Includes foreground importance check branching to prepare for the non-activity
launch path (next PR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the app starts without launching an activity (service, broadcast
receiver, content provider), create a standalone app start transaction
with the end time determined by priority:

1. onApplicationPostCreate (Gradle plugin bytecode instrumentation)
2. ApplicationStartInfo timestamps (API 35+)
3. firstIdle - main thread idle handler (pre-API 35 fallback)

The non-activity app start transaction stores its trace ID so that if
an activity is later launched, the activity transaction reuses the same
trace ID to keep both in the same trace.

Adds OnNoActivityStartedListener callback from AppStartMetrics to
ActivityLifecycleIntegration, triggered by checkCreateTimeOnMain()
when no activity was created after Application.onCreate().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…entation

When an app is launched via broadcast receiver, service, or content provider
(no activity), detect this via Handler.post() and create a standalone app start
transaction. Resolves app start end time with priority: Gradle plugin >
ApplicationStartInfo (API 35+) > process init time. Also attaches child spans
(process init, content providers, Application.onCreate) to standalone
transactions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the "try appStartSpan, fall back to sdkInitTimeSpan" logic used for
standalone (non-activity) app start transactions into a new
AppStartMetrics.getAppStartTimeSpanDirect() helper, removing the duplicated
inline fallback in ActivityLifecycleIntegration and the private helper in
PerformanceAndroidEventProcessor.

Also cache the API 35+ ApplicationStartInfo on registerLifecycleCallbacks so
onAppStartSpansSent no longer re-queries ActivityManager, and simplify the
non-activity detection path to always use the main-thread IdleHandler.

Regenerates the sentry-android-core API to include method additions missed in
prior commits on this branch (standalone-app-start options, trace id accessors,
OnNoActivityStartedListener).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires up the TestBroadcastReceiver added earlier so the sample app can trigger
a non-activity cold start via `adb shell am broadcast`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tart-tracing

# Conflicts:
#	sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java
…icate emission

Two pre-merge fixes for the standalone app-start tracing path introduced on
this branch (issue #5046):

- AppStartMetrics.checkCreateTimeOnMain() now defaults appStartType to COLD
  when UNKNOWN with no active activities. On API < 35 (where
  ApplicationStartInfo is unavailable) non-activity cold starts were stuck
  at UNKNOWN, which both misclassified the standalone transaction as
  App Start Warm and caused PerformanceAndroidEventProcessor.attachAppStartSpans
  to early-return (dropping process.load / application.load / contentprovider.load
  phase spans).

- ActivityLifecycleIntegration.onActivityPreCreated() now skips emitting a
  second standalone App Start transaction when the non-activity path has
  already reported the process's app start (detected via the stashed
  appStartTraceId). Previously a broadcast followed by an activity launch
  produced two standalone transactions (a spurious App Start Warm in addition
  to the broadcast's App Start Cold), violating one-per-process semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines +177 to +184
/** Trace ID from a non-activity app start transaction, to be reused by a later activity. */
public @Nullable SentryId getAppStartTraceId() {
return appStartTraceId;
}

public void setAppStartTraceId(final @Nullable SentryId traceId) {
this.appStartTraceId = traceId;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

later on this should generally be fixed to be trace connected by session and not only the app start + ui.load transactions

@github-actions
Copy link
Copy Markdown
Contributor

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 5387e76

@sentry
Copy link
Copy Markdown

sentry Bot commented Apr 28, 2026

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.40.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions
Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 329.13 ms 392.38 ms 63.25 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9770665 315.64 ms 378.00 ms 62.36 ms
cf708bd 434.73 ms 502.96 ms 68.22 ms
d501a7e 348.06 ms 431.42 ms 83.36 ms
72020f8 312.32 ms 370.94 ms 58.62 ms
b750b96 421.25 ms 444.09 ms 22.84 ms
319f256 317.53 ms 370.83 ms 53.29 ms
c8125f3 397.65 ms 485.14 ms 87.49 ms
55aaf9b 310.45 ms 352.56 ms 42.12 ms
b8bd880 314.56 ms 336.50 ms 21.94 ms
f064536 349.86 ms 417.66 ms 67.80 ms

App size

Revision Plain With Sentry Diff
9770665 0 B 0 B 0 B
cf708bd 1.58 MiB 2.11 MiB 539.71 KiB
d501a7e 0 B 0 B 0 B
72020f8 1.58 MiB 2.19 MiB 620.21 KiB
b750b96 1.58 MiB 2.10 MiB 533.20 KiB
319f256 1.58 MiB 2.19 MiB 619.79 KiB
c8125f3 1.58 MiB 2.10 MiB 532.32 KiB
55aaf9b 0 B 0 B 0 B
b8bd880 1.58 MiB 2.29 MiB 722.92 KiB
f064536 1.58 MiB 2.20 MiB 633.90 KiB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Send standalone app start transactions

1 participant